package sk.ygor.raynor.client.reflection;

import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import sk.ygor.raynor.client.json.JSONSerializer;
import sk.ygor.raynor.client.service.JSONService;

public class JSONStructureViewer extends Composite {

    final JSONStructure structure;
    final JSONService.Transformer serviceTransformer;
    final JSONSerializer.Transformer typeTransformer;

    public JSONStructureViewer(JSONStructure structure, JSONService.Transformer serviceTransformer, JSONSerializer.Transformer typeTransformer) {
        this.structure = structure;
        this.serviceTransformer = serviceTransformer;
        this.typeTransformer = typeTransformer;
        initWidget(createGUI());
    }

    private SimplePanel createGUI() {
        final SimplePanel panel = new SimplePanel();
        final Tree tree = new Tree();
        tree.addItem(root());
        panel.add(tree);
        return panel;
    }

    private TreeItem root() {
        final TreeItem root = new TreeItem("ROOT");
        root.addItem(createServices());
        root.addItem(createTypes());
        return root;
    }

    private TreeItem createServices() {
        final TreeItem services = new TreeItem("SERVICES");
        for (JSONServiceInfo serviceInfo : structure.services().values()) {
            services.addItem(createService(serviceInfo));
        }
        services.setState(true);
        return services;
    }

    private TreeItem createService(JSONServiceInfo serviceInfo) {
        final TreeItem service = new TreeItem(externalServiceName(serviceInfo));
        for (JSONServiceInfo.ServiceMethod serviceMethod : serviceInfo.methods) {
            service.addItem(serviceMethod(serviceMethod));
        }

        return service;
    }

    private String externalServiceName(JSONServiceInfo serviceInfo) {
        return serviceTransformer.transformExternalServiceName(
                serviceInfo.servicePackageName, serviceInfo.serviceClassName
        );
    }

    private TreeItem serviceMethod(JSONServiceInfo.ServiceMethod serviceMethod) {
        final TreeItem method = new TreeItem(serviceMethod.name);
        for (JSONServiceInfo.ServiceMethod.Parameter parameter : serviceMethod.parameters) {
            method.addItem(parameter(parameter));
        }
        method.addItem("------> " + externalTypeName(serviceMethod.returnTypeRef));
        return method;
    }

    private TreeItem parameter(JSONServiceInfo.ServiceMethod.Parameter parameter) {
        return new TreeItem(
                parameter.name + " : " + externalTypeName(parameter.typeRef)
        );
    }

    private String externalTypeName(String typeRef) {
        final JSONTypeInfo typeInfo = structure.types().get(typeRef);
        return externalTypeName(typeInfo);
    }

    private String externalTypeName(JSONTypeInfo typeInfo) {
        if (typeInfo instanceof JSONTypeInfo.BaseType) {
            return ((JSONTypeInfo.BaseType) typeInfo).nature.name();
        } else if (typeInfo instanceof JSONTypeInfo.CollectionType) {
            final JSONTypeInfo.CollectionType collectionType = (JSONTypeInfo.CollectionType) typeInfo;
            return collectionType.nature.name() + " of " + externalTypeName(collectionType.memberTypeRef);
        } else if (typeInfo instanceof JSONTypeInfo.GenericType) {
            JSONTypeInfo.GenericType genericType = (JSONTypeInfo.GenericType) typeInfo;
            return typeTransformer.formatExternalClassName(genericType.parametrizedClassName);
        } else {
            throw new IllegalStateException();
        }
    }

    private TreeItem createTypes() {
        final TreeItem types = new TreeItem("TYPES");
        for (JSONTypeInfo typeInfo : structure.types().values()) {
            if (typeInfo instanceof JSONTypeInfo.BaseType) {
//                types.addItem(new TreeItem(externalTypeName(typeInfo)));
            } else if (typeInfo instanceof JSONTypeInfo.CollectionType) {
//                types.addItem(new TreeItem(externalTypeName(typeInfo)));
            } else if (typeInfo instanceof JSONTypeInfo.GenericType) {
                types.addItem(genericTypeNode((JSONTypeInfo.GenericType) typeInfo));
            } else {
                throw new IllegalStateException();
            }
        }
        return types;
    }

    private TreeItem genericTypeNode(JSONTypeInfo.GenericType genericType) {
        final TreeItem genericTypeNode = new TreeItem(externalTypeName(genericType));
        if (genericType.maybeAttributes instanceof JSONTypeInfo.GenericType.NoAttributes) {
            final JSONTypeInfo.GenericType.NoAttributes noAttributes = (JSONTypeInfo.GenericType.NoAttributes) genericType.maybeAttributes;
            genericTypeNode.addItem("Not serializable: " + noAttributes.reason);
        } else if (genericType.maybeAttributes instanceof JSONTypeInfo.GenericType.HasAttributes) {
            final JSONTypeInfo.GenericType.HasAttributes hasAttributes = (JSONTypeInfo.GenericType.HasAttributes) genericType.maybeAttributes;
            genericTypeNode.addItem(genericTypeAttributesNode(hasAttributes.attributes));
        } else {
            throw new IllegalStateException();
        }
        genericTypeNode.addItem(genericTypeSubTypesNode(genericType.subTypeRefs));
        return genericTypeNode;
    }

    private TreeItem genericTypeSubTypesNode(String[] subTypeRefs) {
        if (subTypeRefs.length == 0) {
            return new TreeItem("No sub-types");
        } else {
            final TreeItem subTypesNode = new TreeItem("Sub-types");
            for (String subTypeRef: subTypeRefs) {
                subTypesNode.addItem(externalTypeName(subTypeRef));
            }
            return subTypesNode;
        }
    }

    private TreeItem genericTypeAttributesNode(JSONTypeInfo.GenericType.Attribute[] attributes) {
        if (attributes.length == 0) {
            return new TreeItem("No attributes");
        } else {
            final TreeItem attributesNode = new TreeItem("Attributes");
            for (JSONTypeInfo.GenericType.Attribute attribute : attributes) {
                attributesNode.addItem(attribute.name + " : " +  externalTypeName(attribute.typeRef));
            }
            return attributesNode;
        }
    }
}
